package classification;

import model.GameRecord;
import model.GameState;
import model.GameType;
import model.Position;
import org.apache.log4j.Logger;
import util.GameLoader;
import util.Util;
import weka.classifiers.Classifier;
import weka.classifiers.trees.RandomForest;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.ArffSaver;
import weka.core.converters.Saver;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * Author: Sergey Melnikov
 */
public class WekaClassifier implements model.Classifier {
    private static final Logger log = Logger.getLogger(WekaClassifier.class);

    private static Instance convertMoveToInstance(Position move, double output) {
        move = Util.getCanonical(move);
        double[] doubles = Util.BITWISE_CONVERTER.apply(move);
        Instance instance = new DenseInstance(doubles.length + 1);
        instance.setDataset(dataset);
        for (int i = 0; i < doubles.length; i++) {
            instance.setValue(i, doubles[i]);
        }
        instance.setValue(doubles.length, (int) (10 * output));
        return instance;
    }

    private static Instance convertPositionToInstance(Position move) {
        return convertMoveToInstance(move, Double.NaN);
    }

    private static List<Instance> gameToListInstance(GameRecord gameRecord) {
        List<Position> winMoves;
        List<Position> looseMoves;
        if (gameRecord.getResult() == GameState.FIRST_PLAYER_WIN) {
            winMoves = gameRecord.getFirstPlayerMoves();
            looseMoves = gameRecord.getSecondPlayerMoves();
        } else {
            winMoves = gameRecord.getSecondPlayerMoves();
            looseMoves = gameRecord.getFirstPlayerMoves();
        }
        List<Instance> result = new ArrayList<>();
        result.addAll(convertMovesToListInstance(winMoves, 1));
        result.addAll(convertMovesToListInstance(looseMoves, -1));
        return result;
    }

    private static List<Instance> gameRecordsToListInstance(List<GameRecord> list) {
        List<Instance> result = new ArrayList<>();
        for (GameRecord gameRecord : list) {
            result.addAll(gameToListInstance(gameRecord));
        }
        return result;
    }

    private static List<Instance> convertMovesToListInstance(List<Position> moves, double multiply) {
        List<Instance> result = new ArrayList<>();
        for (int i = 0; i < moves.size(); i++) {
            result.add(convertMoveToInstance(moves.get(i), multiply * (i + 1) / moves.size()));
        }
        return result;
    }

    private static Instances loadFromArff(File file) throws IOException {
        FileReader reader = new FileReader(file);
        Instances instances = new Instances(reader);
        reader.close();
        instances.setClassIndex(instances.numAttributes() - 1);
        return instances;
    }

    private static Instances dataset;

    private static Classifier teach(String filename) throws Exception {
        Instances data = loadFromArff(new File(filename));
        dataset = data;
//        J48 tree = new J48();
//        RandomForest tree = new RandomForest();
        File file = new File("classifier.ser");
        if (!file.exists()) {
            RandomForest tree = new RandomForest();
            String[] options = {"-I", "20", "-depth", "20", "-num-slots", "4"};
            tree.setOptions(options);
            tree.buildClassifier(data);
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(tree);
            oos.close();
            return tree;
        }
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        Classifier classifier = (Classifier) ois.readObject();
        ois.close();
        return classifier;
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            System.err.println("Usage: from to");
        }
        ArffSaver arffSaver = new ArffSaver();
        List<GameRecord> gameRecords = GameLoader.load(args[0], GameType.IMPROVED);
        arffSaver.setRetrieval(Saver.INCREMENTAL);
        arffSaver.setFile(new File(args[1]));
        ArrayList<Attribute> attributes = new ArrayList<>();
        for (int i = 0; i < 17 * 5 + 1; i++) {
            if (i < 17 * 5) {
                Attribute attribute = new Attribute("a" + i, Arrays.asList("0", "1"));
                attributes.add(attribute);
            } else {
                ArrayList<String> arrayList = new ArrayList<>();
                for (int j = -10; j <= 10; j++) {
                    arrayList.add("" + j);
                }
                Attribute attribute = new Attribute("result", arrayList);
                attributes.add(attribute);
            }
        }
        arffSaver.setStructure(new Instances("name", attributes, 0));
        int count = 0;
        for (GameRecord gameRecord : gameRecords) {
            if (++count % 100 == 0) {
                log.fatal("" + count);
            }
            for (Instance instance : gameToListInstance(gameRecord)) {
                arffSaver.writeIncremental(instance);
            }
        }
        arffSaver.writeIncremental(null);
    }

    private Classifier classifier;

    public WekaClassifier(String filename) {
        try {
            classifier = teach(filename);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public double classify(Position position) {
        try {
            return classifier.classifyInstance(convertPositionToInstance(position));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
